/*

  Debugging heap corruptions in 'C'.

  Compiling notes:

  1. Compile with -DINT16 if you are using 16-bit integers.

  2. Compile with -DUSE_MACROS to use in-line code. 

  3. Compile with -DTEST to create a simple test program.

  4. Check the settings of ALIGN, HEAPCHUNKS and UPPERLIM below.

  5. In general, cc -g -o malloc -DTEST malloc.c

  C offers a flexible mechanism for dynamic allocation of data
  structures via the 'malloc', 'free' and 'realloc' calls.

  Unfortunately, it is possible for a bug in your program to
  corrupt the heap - just one corrupt pointer will destroy the
  program. Consider the following trivial (and obviously flawed)
  example:

  char *cp;

  cp = malloc(10); 
  *(cp - 1) = ' '; 

  this sets the byte just before *cp to space - outside the allocated
  area, writing over the heap link structure. This would not be
  allowed in Pascal (at least not as easily) and is an example of the
  sometimes dangerous flexibility of C.  Corruptions in the heap are
  very hard to track down as they are not detected until sometime
  later.  To debug these problems, I have written a replacement
  'malloc', realloc and 'free' routine which do much more checking of
  the data structure and which stop as soon as a problem is detected.
  Information is displayed which should help in diagnosing the
  problem.  Note that these routines are not particularly fast and
  would not normally be used except in debugging.

  You can call check_entire_heap() at any time.

  You can call dump_heap() at any time (good idea just before
  terminating).

  You can call set_big_check(0) to turn off the massive checking at
  any time.  Call set_big_check(1) to turn it on again. The default is
  on.

  You need to #include "malloc.h" to use the file name & number display 
  feature.

  The calls malloc(), realloc() and free() use (char *) arguments - ie
  what the client programmer sees. The calls _test_malloc(),
  _test_realloc() and _test_free() use (MALLOC_HDR) arguments - ie
  physical heap blocks.  
  */

#include <stdio.h>

/*************************************************************
 * Configurable parameters - these depend on system/compiler *
 *************************************************************/

#define ALIGN 		4 /* most general value for 80x86 - you might trim this */
/*#define ALIGN 		8 /* most general value for RISC */
#define HEAPCHUNKS	32000 /* Only used for HEAPSIZE */
#define HEAPSIZE	((size_t)ALIGN*HEAPCHUNKS)
#define UPPERLIM 	4096		/* = largest block allowed */

/* END OF CONFIGURABLE PARAMETERS - you should not need to change anything 
   after this (except for new ports & bugs, of course) */

#ifdef __STDC__	/* encompass ANSI and non-ANSI function prototypes */

#define NOARGS	(void)
#define ARG1(t1, v1) (t1 v1)
#define ARG2(t1, v1, t2, v2) (t1 v1, t2 v2)
#define ARG3(t1, v1, t2, v2, t3, v3) (t1 v1, t2 v2, t3 v3)
#define ARG4(t1, v1, t2, v2, t3, v3, t4, v4) (t1 v1, t2 v2, t3 v3, t4 v4)
#define ARG5(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5) (t1 v1, t2 v2, t3 v3, t4 v4, t5 v5)
#define ARG6(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6) (t1 v1, t2 v2, t3 v3, t4 v4, t5 v5, t6 v6)
#define ARG7(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6, t7, v7) (t1 v1, t2 v2, t3 v3, t4 v4, t5 v5, t6 v6, t7 v7)

#define HARG1(t1, v1) (t1 v1)
#define HARG2(t1, v1, t2, v2) (t1 v1, t2 v2)
#define HARG3(t1, v1, t2, v2, t3, v3) (t1 v1, t2 v2, t3 v3)
#define HARG4(t1, v1, t2, v2, t3, v3, t4, v4) (t1 v1, t2 v2, t3 v3, t4 v4)
#define HARG5(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5) (t1 v1, t2 v2, t3 v3, t4 v4, t5 v5)
#define HARG6(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6) (t1 v1, t2 v2, t3 v3, t4 v4, t5 v5, t6 v6)
#define HARG7(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6, t7, v7) (t1 v1, t2 v2, t3 v3, t4 v4, t5 v5, t6 v6, t7 v7)

#else

#define const
#define NOARGS ()
#define ARG1(t1, v1) (v1) t1 v1;
#define ARG2(t1, v1, t2, v2) (v1, v2) t1 v1; t2 v2;
#define ARG3(t1, v1, t2, v2, t3, v3) (v1, v2, v3) t1 v1; t2 v2; t3 v3;
#define ARG4(t1, v1, t2, v2, t3, v3, t4, v4) (v1, v2, v3, v4) t1 v1; t2 v2; t3 v3; t4 v4;
#define ARG5(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5) (v1, v2, v3, v4, v5) t1 v1; t2 v2; t3 v3; t4 v4; t5 v5;
#define ARG6(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6) (v1, v2, v3, v4, v5, v6) t1 v1; t2 v2; t3 v3; t4 v4; t5 v5; t6 v6;
#define ARG7(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6, t7, v7) (v1, v2, v3, v4, v5, v6, v7) t1 v1; t2 v2; t3 v3; t4 v4; t5 v5; t6 v6; t7 v7;

#define HARG1(t1, v1) ()
#define HARG2(t1, v1, t2, v2) ()
#define HARG3(t1, v1, t2, v2, t3, v3) ()
#define HARG4(t1, v1, t2, v2, t3, v3, t4, v4) ()
#define HARG5(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5) ()
#define HARG6(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6) ()
#define HARG7(t1, v1, t2, v2, t3, v3, t4, v4, t5, v5, t6, v6, t7, v7) ()

extern int	abs	HARG1(int,	i); 
extern int	atoi	HARG1(char *,	c);
extern double	atof	HARG1(char *,	c);
extern long	atol	HARG1(char *,	c);
extern int	errno;
#endif /* !__STDC__ */

#define ROUNDUP(size) ((ALIGN * (1 + ((size) - 1) / ALIGN)))

#ifdef __TURBOC__
#undef DUMP_CORE
#else
#define DUMP_CORE 1
#endif

#define BITSPERBYTE 8
#define BITS(type)	(BITSPERBYTE * (int)sizeof(type))
#define SIZE_T_BITS	BITS(size_t)

#define MIN_SIZE_T	(1 << (SIZE_T_BITS - 1))  /* 1000 0000 0000 0000 */

/* All this junk goes ahead of the malloc'd data area. Obviously it is essential
   that the malloc'd data area is aligned for any possible data type - hence the
   use of ALIGN and ROUNDUP */

#define SIZE_OF_HDR (sizeof(void *)*2+sizeof(size_t)+sizeof(unsigned)+sizeof(char)*2)
#define FILLER_SIZE (ROUNDUP(SIZE_OF_HDR) - SIZE_OF_HDR)

struct malloc_header_s {
    struct malloc_header_s
		*nextblock; /* Next free or alloc'd block OR malloc_global.heap_end */
    char	*filename;  /* Name of source file that malloc'd this block */	
    size_t	size;	    /* Size malloc'd (not necessarily size of block */
    unsigned	linenum;    /* Linenumber of source file that malloc'd this block */
    char	free;	    /* Is this block free? */
    char	filler[FILLER_SIZE];
    char	guard;	    /* Has this header been corrupted? */
};
typedef struct malloc_header_s *MALLOC_HDR;

#define HDR_SIZE 	(malloc_global.hdr_size)
#define IS_FREE(x) 	((x)->free)
#define SIZE(x)		((x)->size)
#define GUARD_BYTE(x)	((x)->guard) /* 1 byte before data!!! */
#define NEXTBLOCK(x)	((x)->nextblock)
#define FILENAME(x)	((x)->filename)
#define LINENUM(x)	((x)->linenum)
#define DATA(x)		(((char *)(x)) + HDR_SIZE)

#define GUARD 		'\307'	/* value to put in guard bytes */

static char heap[HEAPSIZE + 1]; /* +1 to allow for guard byte to be written at end */

/* This is my naughty global area - at least it's tidy! */
struct malloc_global_struct {
    int 	initialised,
    		free_calls,
		malloc_calls,
		realloc_calls,
		max_alloc,
		hdr_size,
    		big_check;

    unsigned	linenum,
		prev_linenum;
    
    char	*entry_point,
		*prev_entry_point,
		*filename,
		*prev_filename;

    MALLOC_HDR
		heap,
		heap_end;
};
static struct malloc_global_struct malloc_global;

/*****************************************************************
 * m_abort - CALLED BY free AND malloc WHEN AN ERROR IS DETECTED *
 *****************************************************************/

static void m_abort ARG1(char *,	s)
{
    fprintf(stderr, "\n\n%s:%u: PREVIOUS ENTRY = %s\n%s:%u: THIS ENTRY = %s\n", 
	    malloc_global.prev_filename, 
	    malloc_global.prev_linenum, 
	    malloc_global.prev_entry_point, 
	    malloc_global.filename, 
	    malloc_global.linenum, 
	    malloc_global.entry_point);
    fputs(s, stderr);
    fputc('\n', stderr);
    fprintf(stderr, "free call number = %d, malloc call number = %d\n", 
	    malloc_global.free_calls, 
	    malloc_global.malloc_calls);
    fprintf(stderr, "realloc call number = %d\n", malloc_global.realloc_calls);
    fprintf(stderr, "maximum alloced block = %d\n", malloc_global.max_alloc);
#ifdef DUMP_CORE
    kill(getpid(), 3);
#else
    exit(1);
#endif
}

/******************************************
 * DO SOME CHECKING ON A HEAP POINTER ... *
 ******************************************/

static void _check_heap_ptr ARG2(MALLOC_HDR, hp, int, linkage)
{
    if (hp == NULL)
	m_abort("malloc: null malloc header pointer!");

    if ((hp < malloc_global.heap) || (hp >= malloc_global.heap_end))
	m_abort("malloc: link points outside heap");

    if (GUARD_BYTE(hp) != GUARD)
	m_abort("malloc: link guard cell corrupt (before malloc'd block)");

    if (NEXTBLOCK(hp) > malloc_global.heap_end)
	m_abort("malloc: link corrupt - NEXTBLOCK points outside heap");

    if (!IS_FREE(hp) && (SIZE(hp) > malloc_global.max_alloc))
	m_abort("malloc: link corrupt - too big");

    if (hp->size > ((char *)hp->nextblock - (char *)hp - HDR_SIZE - 1))
	m_abort("malloc: size or nextblock corrupt");

    if (*(DATA(hp) + SIZE(hp)) != GUARD)
	m_abort("malloc: end of data written on");

    if (linkage) {
	MALLOC_HDR hpp;
	
	for (hpp = malloc_global.heap; 
	     hpp < malloc_global.heap_end; 
	     hpp = NEXTBLOCK(hpp))
	    if (hpp == hp)
		return;
	m_abort("malloc: heap pointer not in list");
    }
}

void check_heap_ptr ARG1(void *, ptr)
{
    _check_heap_ptr((MALLOC_HDR) (ptr - HDR_SIZE), 1);
}

void check_entire_heap()
{
    MALLOC_HDR	hp;

    for (hp = malloc_global.heap; hp < malloc_global.heap_end; hp = NEXTBLOCK(hp))
	_check_heap_ptr(hp, 0);
}

/******************************
 * Merge adjacent free blocks *
 ******************************/

static void mergeBlocks ARG1(MALLOC_HDR, hp)
{
    MALLOC_HDR	nb;

    _check_heap_ptr(hp, 1);

    while (((nb  = NEXTBLOCK(hp)) < malloc_global.heap_end) && IS_FREE(nb))
	NEXTBLOCK(hp) = NEXTBLOCK(nb);
}

/************************
 * MARK A BLOCK AS FREE *
 ************************/

static void freeBlock ARG1(MALLOC_HDR,	hp)
{
    if ((char *) NEXTBLOCK(hp) - (char *) hp - 1 < HDR_SIZE)
	m_abort("malloc: freeBlock of too small a block");

    GUARD_BYTE(hp) = GUARD;
    SIZE(hp) = (char *) NEXTBLOCK(hp) - (char *) hp - HDR_SIZE - 1;
    *(DATA(hp) + SIZE(hp)) = GUARD;
    IS_FREE(hp) = 1;
    FILENAME(hp) = NULL;
    LINENUM(hp) = 0;
    mergeBlocks(hp);
}

/************************
 * MARK A BLOCK AS USED *
 ************************/

static void markBlock ARG4(MALLOC_HDR,	hp, 
			   size_t,	size,
			   char *,	filename,
			   unsigned,	linenum)
{
    size_t	remainder, round_size;

    _check_heap_ptr(hp, 1);

    round_size = ROUNDUP(size + 1 + HDR_SIZE);

    SIZE(hp) = size;
    *(DATA(hp) + size) = GUARD;
    IS_FREE(hp) = 0;
    FILENAME(hp) = filename;
    LINENUM(hp) = linenum;
    GUARD_BYTE(hp) = GUARD;

    if (size > malloc_global.max_alloc)
	malloc_global.max_alloc = size;
    
    /* create a new free block with anything left over */
    if ((char *) NEXTBLOCK(hp) - (char *) hp < round_size)
	m_abort("malloc: too small a block to mark");

    remainder = (char *) NEXTBLOCK(hp) - (char *) hp - round_size;

    /* ... but, don't alloc a tiny block ... */
    if (remainder > HDR_SIZE + 8) {
	MALLOC_HDR	nb;

	nb = (MALLOC_HDR) ((char *) hp + round_size);
	
	NEXTBLOCK(nb) = NEXTBLOCK(hp);
	NEXTBLOCK(hp) = nb;
	freeBlock(nb);
    }
}

/************************************
 * free - WITH DEBUGGING STATEMENTS *
 ************************************/

static void _test_free ARG1(MALLOC_HDR, hp)
{
    if (hp == NULL)
	m_abort("free called with null pointer");

    _check_heap_ptr(hp, 1);

    if (IS_FREE(hp))
	m_abort("attempt to free an unallocated block");

    freeBlock(hp);

    if (malloc_global.big_check)
	check_entire_heap();
}

void test_free ARG3(void *, cp, char *, filename, unsigned, linenum)
{
    if (!(malloc_global.initialised))
	m_abort("malloc: free called before any malloc");

    malloc_global.free_calls++;
    malloc_global.prev_entry_point = malloc_global.entry_point;
    malloc_global.prev_filename = malloc_global.filename;
    malloc_global.prev_linenum = malloc_global.linenum;
    malloc_global.filename = filename;
    malloc_global.linenum = linenum;
    malloc_global.entry_point = "FREE";

    _test_free((MALLOC_HDR) ((char *)cp - HDR_SIZE));
}

static void initialise_globals()
{
    malloc_global.initialised = 1;
    malloc_global.free_calls = 0;
    malloc_global.malloc_calls = 0;
    malloc_global.realloc_calls = 0;
    malloc_global.max_alloc = 0;
    malloc_global.hdr_size = ROUNDUP(sizeof(struct malloc_header_s));
    malloc_global.big_check = 1;

    malloc_global.linenum = 0;
    malloc_global.prev_linenum = 0;
    
    malloc_global.entry_point = "";
    malloc_global.prev_entry_point = "";
    malloc_global.filename = "";
    malloc_global.prev_filename = "";
    malloc_global.heap = (MALLOC_HDR) heap;
    malloc_global.heap_end = (MALLOC_HDR) ((char *) heap + HEAPSIZE);

    NEXTBLOCK(malloc_global.heap) = malloc_global.heap_end;
    freeBlock(malloc_global.heap);
}

/**************************************
 * malloc - WITH DEBUGGING STATEMENTS *
 **************************************/

static MALLOC_HDR _test_malloc ARG3(size_t,	size,
				    char *,	filename,
				    unsigned,	linenum)
{
    MALLOC_HDR	hp;
    size_t 	remainder, round_size;

    if (!malloc_global.initialised) 
	initialise_globals();

    if (size <= 0)
	m_abort("attempt to malloc zero or negative number");
    if (size > UPPERLIM)
	m_abort("attempt to allocate too large a block");

    round_size = ROUNDUP(size + 1 + HDR_SIZE);

    if (malloc_global.big_check)
	check_entire_heap();

    for (hp = malloc_global.heap;
	 hp < malloc_global.heap_end; 
	 hp = NEXTBLOCK(hp)) {
	if (!malloc_global.big_check)
	    _check_heap_ptr(hp, 1);

	if (IS_FREE(hp)) {
	    mergeBlocks(hp);

	    /* see if this one is big enough */
	    if (round_size <= ((char *) NEXTBLOCK(hp) - (char *) hp)) {
		/* allocate the block */
		markBlock(hp, size, filename, linenum);
		memset(DATA(hp), 0, size);
		return(hp);
	    }
	}
    }
    m_abort("malloc: ran out of heap");
    return(NULL);
}

void *test_malloc ARG3(size_t, size, char *, filename, unsigned, linenum)
{
    malloc_global.malloc_calls++;
    malloc_global.prev_entry_point = malloc_global.entry_point;
    malloc_global.prev_filename = malloc_global.filename;
    malloc_global.prev_linenum = malloc_global.linenum;
    malloc_global.filename = filename;
    malloc_global.linenum = linenum;
    malloc_global.entry_point = "MALLOC";

    return(DATA(_test_malloc(size, filename, linenum)));
}

 /***************************************
  * realloc - WITH DEBUGGING STATEMENTS *
 /***************************************/

MALLOC_HDR _test_realloc ARG4(MALLOC_HDR,	old, 
			      size_t,		size,
			      char *,		filename,
			      unsigned,		linenum)
{
    MALLOC_HDR	hp;
    size_t	round_size;

    _check_heap_ptr(old, 1);

    if (malloc_global.big_check)
	check_entire_heap();

    if (size <= 0)
	m_abort("attempt to realloc zero or negative number");

    if (size > UPPERLIM)
	m_abort("attempt to realloc too large a block");

    /* round size up to alignment */
    round_size = ROUNDUP(size + 1 + HDR_SIZE);

    hp = old;

    /* See if the next block is free - expand to suit */
    while ((NEXTBLOCK(hp) < malloc_global.heap_end) && 
	   IS_FREE(NEXTBLOCK(hp)) &&
	   (round_size > ((char *) NEXTBLOCK(hp)) - (char *) hp)) {
	if (!(malloc_global.big_check))
	    _check_heap_ptr(NEXTBLOCK(hp), 1);
	NEXTBLOCK(hp) = NEXTBLOCK(NEXTBLOCK(hp));
    }

    /* Do it simply! ... */
    if (round_size <= ((char *) NEXTBLOCK(hp)) - (char *) hp) {
	markBlock(hp, size, filename, linenum);
	return(hp);
    }

    /* Final resort ... */
    hp = _test_malloc(size, filename, linenum); /* won't return NULL - will abort */
    memcpy(DATA(hp), DATA(old), SIZE(hp) < size? SIZE(hp): size);
    *(DATA(hp) + size) = GUARD;
    _test_free(old);

    return(hp);
}

void *test_realloc ARG4(void *,		old, 
			size_t,		size,
			char *,		filename,
			unsigned,	linenum)
{
    if (!(malloc_global.initialised))
	m_abort("malloc: realloc called before any malloc");

    malloc_global.realloc_calls++;
    malloc_global.prev_entry_point = malloc_global.entry_point;
    malloc_global.prev_filename = malloc_global.filename;
    malloc_global.prev_linenum = malloc_global.linenum;
    malloc_global.entry_point = "REALLOC";
    malloc_global.filename = filename;
    malloc_global.linenum = linenum;

    return(DATA(_test_realloc((MALLOC_HDR)((char *) old - HDR_SIZE), 
			      size, filename, linenum)));
}

/* Usually called just before termination */
void dump_heap()
{
    MALLOC_HDR	hp;
    char	*data, *end;

    check_entire_heap();

    fprintf(stderr, "\n=============Start of heap dump=============\n");
    for (hp = malloc_global.heap; hp < malloc_global.heap_end; hp = NEXTBLOCK(hp))
	if (!IS_FREE(hp)) {
	    fprintf(stderr,"%s:%u:malloc: block of size %d:", 
		    FILENAME(hp), LINENUM(hp), SIZE(hp));
	    data = DATA(hp);
	    end = data + SIZE(hp);
	    for (; data < end; data++)
		if ((*data >= ' ') && (*data <= '~'))
		    fputc(*data, stderr);
		else
		    fprintf(stderr, "\\%o ", *data);
	    fputc('\n', stderr);
	}
    fprintf(stderr, "==============End of heap dump==============\n");
}

void set_big_check ARG1(int,	i)
{
    malloc_global.big_check = i;
}

void *test_xmalloc ARG3(size_t, 	size,
			char *,		filename,
			unsigned,	linenum) 
{ 
    return(test_malloc(size, filename, linenum)); 
}

void *xmalloc ARG1(size_t, 	size)
{ 
    return(test_malloc(size, "", 0)); 
}

void *test_xrealloc ARG4(void *, 	old, 
			 size_t, 	size,
			 char *,	filename,
			 unsigned,	linenum) 
{ 
    if (old)
	return(test_realloc(old, size, filename, linenum));
    else
	return(test_malloc(size, filename, linenum));
}

void *xrealloc ARG2(void *, 	old, 
		    size_t, 	size)
{ 
    if (old)
	return(test_realloc(old, size, "", 0));
    else
	return(test_malloc(size, "", 0));
}

#ifdef TEST
#define NUMBUF 20
#define BUFSIZE 100
#define MAX_ITERATIONS 3000

/* test driver for malloc & free ... usage: malloc [Numbuf [Bufsize [Iterations]]] */
#include <stdlib.h>
#include "malloc.h"

/* Return random int from 0 to i-1 */
int my_random ARG1(int, i)
{
    return(random() / (RAND_MAX / i));
}

main(argc, argv)
    int		argc;
    char	**argv;
{
    char 	**t;
    int 	i, j;
    int		numbuf = NUMBUF;
    int		bufsize = BUFSIZE;
    long	max_iterations, iteration, atol();

#if 0
    fprintf(stderr, "Sizeof(struct malloc_header_s) = %d\n", sizeof(struct malloc_header_s));
    fprintf(stderr, "Sizeof(unsigned) = %d\n", sizeof(unsigned));
    fprintf(stderr, "Sizeof(size_t) = %d\n", sizeof(size_t));
    fprintf(stderr, "FILLER_SIZE = %d\n", FILLER_SIZE);
    fprintf(stderr, "SIZE_OF_HDR = %d\n", SIZE_OF_HDR);
    fprintf(stderr, "ROUNDUP(SIZE_OF_HDR) = %d\n", ROUNDUP(SIZE_OF_HDR));
#endif

    if (argc > 1)
	numbuf = atoi(argv[1]);
    
    if (argc > 2)
	bufsize = atoi(argv[2]);
    
    if (argc > 3)
	max_iterations = atol(argv[3]);
    else
	max_iterations = MAX_ITERATIONS;

    /* malloc numbuf buffers of size bufsize , 
       realloc them,
       initialise them, 
       check them,
       free them */

    t = (char **) malloc(sizeof(char *) * numbuf);

    for (i = 0; i < numbuf; i++) {
	t[i] = malloc(bufsize);
	for (j = 0; j < bufsize; j++)
	    t[i][j] = i;
    }

    for (i = 0; i < numbuf; i++) {
	t[i] = realloc(t[i], bufsize*2);
	for (j = bufsize; j < bufsize*2; j++)
	    t[i][j] = i;
    }

    for (i = 0; i < numbuf; i++) {
	for (j = 0; j < bufsize*2; j++)
	    if (t[i][j] != i) {
		fprintf(stderr, "Whoops!\n");
		exit(2);
	    }
	free(t[i]);
	t[i] = NULL;
    }

#if 0 /* Corrupt the heap before a block */
    {
	char *p = (char *)t;
	
	*(p - 1) = 0;
    }
#endif /* Corrupt the heap after a block */

#if 0 
    *((char *)t + sizeof(char *) * numbuf) = 0;
#endif

/* Random mallocs & reallocs */
    iteration = 0;
    while (iteration < max_iterations) {
	int	size = my_random(200) + 1;

	iteration++;
	for (i = 0; i < numbuf; i++)
	    if (t[i])
		check_heap_ptr(t[i]);

	i = my_random(numbuf);
	if (t[i] == NULL)
	    t[i] = malloc(size);
	else {
	    int choice = my_random(2);
	    if (choice)
		t[i] = realloc(t[i], size);
	    else {
		free(t[i]);
		t[i] = NULL;
	    }
	}
    }

    for (i = 0; i < numbuf; i++)
	if (t[i]) {
	    free(t[i]);
	    t[i] = NULL;
	}

#if 1 /* forget to free a block? */
    free((char *)t);
    t = NULL;
#endif

    fprintf(stderr, "%d mallocs, %d reallocs & %d frees executed OK\n", 
	    malloc_global.malloc_calls, 
	    malloc_global.realloc_calls, 
	    malloc_global.free_calls);
    fprintf(stderr, "Maximum alloc'd block = %d\n", malloc_global.max_alloc);
    dump_heap();
    exit(0);
}

#endif /* TEST */

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
